package movement

import (
	b "code.google.com/p/gochess/board"
	pos "code.google.com/p/gochess/position"
)

var Stats struct {
	DetectCheck,
	DetectCheckByLastMove,
	GenerateCastlings,
	GenerateCheckEvasions,
	GeneratePawnCapturesAndPromotions,
	GeneratePawnCapturesAndQueenPromotions,
	GeneratePawnMoves,
	GeneratePieceCaptures,
	GeneratePieceMoves,
	IsCheck,
	IsLegalMoveWhileNotInCheck,
	IsLegalMoveWhileNotInCheck1,
	IsLegalMoveWhileNotInCheck2,
	IsLegalMoveWhileNotInCheck3,
	IsLegalMoveWhileNotInCheck4,
	IsLegalMoveWhileNotInCheck5,
	IsLegalMoveWhileNotInCheck6,
	IsPinned, IsPinned1, IsPinned2,
	IsQuietPseudoLegalMove,
	MaxRayNoPieceType, MaxRayQueen, MaxRayRook, MaxRayBishop,
	MoveInfo,
	PositionBonus,
	UnderAttack int64
}

func init() {
	generatePossibleMoves()
}

// Movements of the pieces, excluding pawns
var movements = [...]struct {
	IsSlider   bool
	Directions []b.Square
}{
	pos.King: {false,
		[]b.Square{b.Up, b.Up + b.Right, b.Right, b.Down + b.Right, b.Down,
			b.Left - b.Up, b.Left, b.Up - b.Right}},
	pos.Queen: {true,
		[]b.Square{b.Up, b.Up + b.Right, b.Right, b.Down + b.Right, b.Down,
			b.Down - b.Right, b.Left, b.Up - b.Right}},
	pos.Rook: {true, []b.Square{b.Up, b.Right, b.Down, b.Left}},
	pos.Bishop: {true, []b.Square{b.Up + b.Right, b.Down + b.Right,
		b.Left - b.Up, b.Up - b.Right}},
	pos.Knight: {false,
		[]b.Square{2*b.Up + b.Right, b.Up + 2*b.Right,
			b.Down + 2*b.Right, b.Down - b.Up + b.Right, b.Down - b.Up - b.Right,
			b.Down - 2*b.Right, b.Up - 2*b.Right, 2*b.Up - b.Right}},
}

const maxSquareDifference = b.H8 - b.A1

// no pawns included
// direction is a problem then, we need the sign of the abs value
var possibleMoves [(maxSquareDifference + 1) * 2]moveInformation

// todo: use this
// for a square difference put the sign on the direction
func sign(dir, diff int8) int8 {
	return dir ^ (diff >> 7) - (diff >> 7)
}

// called from init()
// todo: we could put dir on non ray squares which would lead to a border piece!
func generatePossibleMoves() {

	for pieceType := range movements {
		for _, direction := range movements[pieceType].Directions {
			for start := b.A1; start <= b.H8; start++ {
				if !b.IsValid(start) {
					continue
				}
				destination := start + direction
				if movements[pieceType].IsSlider {
					for ; b.IsValid(destination); destination += direction {
						index := destination - start + maxSquareDifference
						possibleMoves[index].setDirection(direction)
						possibleMoves[index].addMover(pos.PieceType(pieceType))
						possibleMoves[index].ray = true
					}
				} else if b.IsValid(destination) {
					index := destination - start + maxSquareDifference
					possibleMoves[index].setDirection(direction)
					possibleMoves[index].addMover(pos.PieceType(pieceType))
				}
			}
		}
	}
}

type moveInformation struct {
	pieceSet  uint32
	direction b.Square
	ray       bool
}

func moveInfo(start, dst b.Square) *moveInformation {
	Stats.MoveInfo++
	return &possibleMoves[dst-start+maxSquareDifference]
}

func (this *moveInformation) isRay() bool {
	return this.ray
}

func (this *moveInformation) canMove(piece pos.PieceType) bool {
	return this.pieceSet>>uint(piece)&0x01 == 1
}

func (this *moveInformation) Direction() b.Square {
	return this.direction
}

func (this *moveInformation) setDirection(value b.Square) {
	this.direction = value
}

func (this *moveInformation) addMover(piece pos.PieceType) {
	this.pieceSet |= 1 << uint(piece)
}

// this approach gives almost nothing
//type moveInformation int32

//func moveInfo(start, dst b.Square) moveInformation {
//	stats.MoveInformationCalls++
//	return possibleMoves[dst-start+maxSquareDifference]
//}

//func (this moveInformation) canMove(piece pos.PieceType) bool {
//	return this>>uint(piece)&0x01 == 1
//}

//func (this moveInformation) Direction() b.Square {
//	return b.Square(this >> 3)
//}

//func (this moveInformation) setDirection(value b.Square) moveInformation {
//	this |= moveInformation(value << 3)
//	return this
//}

//func (this moveInformation) addMover(piece pos.PieceType) moveInformation {
//	this |= 1 << uint(piece)
//	return this
//}

// todo: walk queen- und knightwise from attacked square, see below
func UnderAttack(p *pos.Position, location b.Square, attacker pos.Color) bool {
	Stats.UnderAttack++
	enemyPawn := pos.NewPiece(attacker, pos.Pawn)
	squareUp := location - pos.MoveDirection(attacker)
	var sq b.Square
	if p.Get(squareUp+b.Left) == enemyPawn || p.Get(squareUp+b.Right) == enemyPawn {
		return true
	}
	piece := pos.NewPiece(attacker, pos.Knight)
	for pt := pos.Knight; pt <= pos.King; pt++ {
		for i := p.PieceList(piece).Max - 1; i >= 0; i-- {
			from := p.PieceList(piece).Pieces[i]
			if !moveInfo(from, location).canMove(pt) {
				continue
			}
			dir := moveInfo(from, location).Direction()
			for sq = from + dir; p.Get(sq) == pos.NoPiece && sq != location; sq += dir {
			}
			if sq == location {
				return true
			}
		}
		piece = piece.Inc()
	}
	return false
}

//func UnderAttack(p *pos.Position, location b.Square, attacker pos.ColorID) bool {

//	UnderAttackCalls++
//	opponent := pos.GetOpponent(attacker)
//	enemyPawn := pos.NewPiece(attacker, pos.Pawn)
//	squareUp := location + pos.GetMoveDirection(opponent)
//	if p.Piece(squareUp+b.Left) == enemyPawn {
//		return true
//	}
//	if p.Piece(squareUp+b.Right) == enemyPawn {
//		return true
//	}

//	for direction := range movements[pos.Queen].Directions {

//		square := location + direction
//		for ; p.Piece(square) == pos.NoPiece; square += direction {

//		}
//		if p.Piece(square).Color() == opponent &&

//	}

//	return false
//}

// IsCheck returns true if color is in check
func IsCheck(p *pos.Position, color pos.Color) bool {
	Stats.IsCheck++
	return UnderAttack(p, p.King(color), pos.Opponent(color))
}
